﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Threading;
using System.Windows.Forms;
using Freedom.Common.Collections;
using log4net;

namespace Freedom.WinCommon.Collections
{
    public abstract class SelfRefreshObjectFilterCollection<T, TFilter> : ObjectFilterCollection<T, TFilter>,
                                                                          ILoadableObject
        where T : class, ICopiable<T>, IIdentifiable
        where TFilter : struct, IComparable, IConvertible, IFormattable
    {
        #region Properties

        private bool _isLoading;
        private bool _needRefresh;
        private bool _needExportData;
        private int? _maxCount;
        private readonly ObjectCollector<T> _loadedItems = new ObjectCollector<T>();
        private static readonly IEqualityComparer<T> _comparer = new IdentifiableComparer<T>();
        private readonly List<T> _completedItems = new List<T>();
        private int _exportCount;
        private int? _privateCount;

        private readonly ILog _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        /// <summary>
        /// Async Load Event Handler
        /// </summary>
        public delegate void AsyncLoadEventHandler();

        /// <summary>
        /// Gets a value indicating whether this instance is loaded.
        /// </summary>
        /// <value><c>true</c> if this instance is loaded; otherwise, <c>false</c>.</value>
        public bool IsLoaded
        {
            get { return !_isLoading; }
        }

        /// <summary>
        /// Gets the loaded items.
        /// </summary>
        public List<T> CompletedItems
        {
            get
            {
                lock (_completedItems)
                {
                    return _completedItems;
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether this instance is refresh.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is refresh; otherwise, <c>false</c>.
        /// </value>
        public bool NeedRefresh
        {
            get { return _needRefresh; }
            set
            {
                bool oldvalue = _needRefresh;
                _needRefresh = value;

                if (!oldvalue && value)
                {
                    Refresh();
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [need export data].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [need export data]; otherwise, <c>false</c>.
        /// </value>
        public bool NeedExportData
        {
            get { return _needExportData; }
            set
            {
                _needExportData = value;
                if (!_needExportData)
                    return;

                _privateCount = _maxCount;
                _maxCount = int.MaxValue;
                IAsyncResult result = ((AsyncLoadEventHandler) AsyncLoad).BeginInvoke(null, null);
                while (result.IsCompleted == false)
                {
                    Thread.Sleep(100);
                }

                _exportCount = Items.Count;
                ((MethodInvoker) StartSelfRefresh).BeginInvoke(null, null);
            }
        }

        /// <summary>
        /// The export record count
        /// </summary>
        public int ExportCount
        {
            get { return _exportCount; }
        }

        /// <summary>
        /// Gets the max count.
        /// </summary>
        public int? MaxCount
        {
            get { return _maxCount; }
            set
            {
                _maxCount = value;
                _privateCount = _maxCount;
                Refresh();
            }
        }

        /// <summary>
        /// Gets the size of the package.
        /// </summary>
        /// <value>The size of the package.</value>
        protected virtual int PackageSize
        {
            get { return 40; }
        }

        /// <summary>
        /// the time invertal start next refresh data
        /// </summary>
        protected virtual int SleepSecondsNextRefresh
        {
            get { return 30; }
        }


        /// <summary>
        /// Gets the comparer.
        /// </summary>
        /// <value>The comparer.</value>
        protected virtual IEqualityComparer<T> Comparer
        {
            get { return _comparer; }
        }

        /// <summary>
        /// Gets the state of the loading.
        /// </summary>
        /// <value>The get loaded items count.</value>
        /// <returns></returns>
        int? ILoadableObject.LoadedItemsCount
        {
            get { return _isLoading ? Items.Count : (int?) null; }
        }

        #endregion

        #region Construction

        /// <summary>
        /// Initializes a new instance of the <see cref="SelfRefreshObjectCollection{T}"/> class.
        /// </summary>
        public SelfRefreshObjectFilterCollection()
        {
            _needRefresh = true;
        }

        #endregion

        #region Load Items

        protected void StartSelfRefresh()
        {
            if (_needRefresh)
            {
                //wait and refresh the collection
                Thread.Sleep(SleepSecondsNextRefresh*1000);
                Refresh();
            }
        }

        /// <summary>
        /// Loads the items.
        /// </summary>
        /// <param name="fromConstructor">if set to <c>true</c> [from constructor].</param>
        protected override void LoadItems(bool fromConstructor)
        {
            if (_isLoading) return;
            Clear();
            ((MethodInvoker) AsyncLoad).BeginInvoke(null, null);
        }

        /// <summary>
        /// Finds the by id.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <returns></returns>
        protected T FindById(object id)
        {
            int index;
            return Find(e => e.Id.Equals(id), out index);
        }

        private void AsyncLoad()
        {
            _isLoading = true;
            int? tempMaxCount = _maxCount;
            try
            {
                var skip = 0;

                //load items from database
                do
                {
                    //if have max load count, when loaded count + packagesize > max count, the read size is maxcount - size, otherwise is packagesize
                    int readSize = tempMaxCount != null && skip + PackageSize > tempMaxCount.Value
                                       ? tempMaxCount.Value - skip
                                       : PackageSize;

                    //if needrefresh is false, return
                    if (!_needRefresh) return;

                    //read data from service
                    var items = ReadPage(skip, readSize);

                    //put loaded items into collection
                    if (items != null)
                    {
                        _loadedItems.Put(items);
                        AddRange(_loadedItems.ExctractAll().Except(Items, Comparer).ToArray());
                    }

                    //set skip value
                    skip += PackageSize;

                    _isLoading = DetermineContinueLoading(items);
                } while (_isLoading && (tempMaxCount == null || skip <= tempMaxCount));

                //add all items to loaded items
                lock (_completedItems)
                {
                    _completedItems.Clear();
                    _completedItems.AddRange(this.ToList());
                }

                //trigger the reset event
                OnCollectionReset();
            }
            catch (EndpointNotFoundException ex)
            {
                Clear();
            }
            catch (CommunicationException ex)
            {
                Clear();
            }
            catch (Exception ex)
            {
                _log.Error(ex);
            }
            finally
            {
                _isLoading = false;

                if (_needRefresh && !_needExportData)
                {
                    //wait and refresh the collection
                    Thread.Sleep(SleepSecondsNextRefresh*1000);
                    Refresh();
                }

                _maxCount = _privateCount;
                _needExportData = false;
            }
        }

        /// <summary>
        /// Reads the page.
        /// </summary>
        /// <param name="startIndex">The start index.</param>
        /// <param name="count">The count.</param>
        protected abstract T[] ReadPage(int startIndex, int count);

        /// <summary>
        /// Determines the continue loading.
        /// </summary>
        /// <param name="readedItems">The readed items.</param>
        /// <returns>True-need load next chunk</returns>
        protected virtual bool DetermineContinueLoading(T[] readedItems)
        {
            if (readedItems == null)
                return false;
            return readedItems.Length == PackageSize;
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _needRefresh = false;
            }
            base.Dispose(disposing);
        }

        #endregion

        #region ILoadableObject Members

        /// <summary>
        /// Updates the state.
        /// </summary>
        public void UpdateState()
        {
            //implement from ILoadableObject
        }

        #endregion
    }
}